﻿using System;
using System.Collections.Specialized;
using System.Globalization;
using System.Text;
using System.Web;
using System.ComponentModel;
using System.Reflection;

namespace Sissonix.ExtRest.Serialization
{
    internal class XWwwFormUrlEncodedSerializer : ISerializer
    {
        public string ContentType
        {
            get { return MimeTypes.APPLICATION_X_WWW_FORM_URLENCODED; }
        }

        public object Deserialize(string value, Type type)
        {
            var formPairs = value.Split(new[] { '&' });
            var formPairKeyValueList = new StringDictionary();
            if (formPairs.Length > 0)
            {
                foreach (var formPair in formPairs)
                {
                    var formPairKeyValue = formPair.Split(new[] { '=' });
                    formPairKeyValueList.Add(HttpUtility.UrlDecode(formPairKeyValue[0]), HttpUtility.UrlDecode(formPairKeyValue[1]));
                }
            }
            else
            {
                var formPairKeyValue = value.Split(new[] { '=' });
                formPairKeyValueList.Add(HttpUtility.UrlDecode(formPairKeyValue[0]), HttpUtility.UrlDecode(formPairKeyValue[1]));
            }
            var deserializedObject = Activator.CreateInstance(type);
            foreach (var property in type.GetProperties())
            {
                foreach (string propertyName in formPairKeyValueList.Keys)
                {
                    if (property.Name.ToUpperInvariant() != propertyName.ToUpperInvariant()) continue;
                    //MWS - Needed to handle nullable object types which EF will have plenty of
                    //var convertedValue = Convert.ChangeType(formPairKeyValueList[propertyName], property.PropertyType, CultureInfo.InvariantCulture);

                    //MWS - must do these gymnastics because the generic ChangeType() function
                    //can't take a variable for it generic value, that must be set during compile time.
                    //Using reflection solves it.
                    Type t = this.GetType();
                    BindingFlags bf = BindingFlags.Static | BindingFlags.Public;
                    MethodInfo mi = t.GetMethod("ChangeType", bf);
                    mi = mi.MakeGenericMethod(property.PropertyType);
                    object convertedValue = mi.Invoke(null, new object[] { formPairKeyValueList[propertyName] });
                    
                    property.SetValue(deserializedObject, convertedValue, null);
                }
            }
            return deserializedObject;
        }

        //MWS - Added this method because the Convert.ChangeType call in Deserialize above
        //      can't handle objects that are nullable (i.e. defined with a question mark like int? abc = "123")
        //      See http://aspalliance.com/852
        public static T ChangeType<T>(object value)
        {
            Type conversionType = typeof(T);
            if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {
                if (value == null)
                {
                    return default(T);
                }
                else
                {
                    System.ComponentModel.NullableConverter nullableConverter = new System.ComponentModel.NullableConverter(conversionType);
                    conversionType = nullableConverter.UnderlyingType;
                }
            }

            if (conversionType == typeof(System.DateTime))
            {
                DateTime dt = new DateTime();
                if (DateTime.TryParse(value.ToString(),out dt) == false)
                    return default(T);
            }
            if (conversionType == typeof(System.Boolean))
            {
                value = (value.ToString() == "on");
            }

            return (T)Convert.ChangeType(value, conversionType);
        }

        
        public string Serialize(object objectToSerialize)
        {
            var type = objectToSerialize.GetType();
            var valueStringBuilder = new StringBuilder();
            var isFirstItem = true;
            foreach (var property in type.GetProperties())
            {
                var value = property.GetValue(objectToSerialize, null);
                if (value == null) continue;
                if (!isFirstItem)
                {
                    valueStringBuilder.AppendFormat("&{0}={1}", HttpUtility.UrlEncode(property.Name), HttpUtility.UrlEncode(value.ToString()));
                }
                else
                {
                    valueStringBuilder.AppendFormat("{0}={1}", HttpUtility.UrlEncode(property.Name), HttpUtility.UrlEncode(value.ToString()));
                    isFirstItem = false;
                }
            }
            return valueStringBuilder.ToString();
        }
    }
}
